package com.blue.crawler.util;

import java.io.IOException;
import java.net.Proxy;
import java.net.Socket;

import javax.net.ssl.SSLContext;

import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.blue.core.util.UrlUtil;


public class CrawlerInvoker
{
	private static Logger logger = LoggerFactory.getLogger(CrawlerInvoker.class);
	
	public final static int TIMEOUT = 120000;
	private final static int MAX_TOTAL = 200;
	private final static int MAX_PER_ROUTE = 200;
	
	private final static String PROXY = "proxy";
	public final static String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:53.0) Gecko/20100101 Firefox/53.0";
	
	private final PoolingHttpClientConnectionManager connMgr;
	private final CloseableHttpClient httpClient;
	
	public CrawlerInvoker()
	{
		RegistryBuilder<ConnectionSocketFactory> registry = RegistryBuilder.create();
		registry.register("http", new ProxyPlainConnectionSocketFactory());
		
		try
		{
			SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (chain, type) -> true).build();
			SSLConnectionSocketFactory sslSF = new ProxySSLConnectionSocketFactory(sslContext);
			registry.register("https", sslSF);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
		SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();
		connMgr = new PoolingHttpClientConnectionManager(registry.build());
		connMgr.setDefaultSocketConfig(socketConfig);
		connMgr.setMaxTotal(MAX_TOTAL);
		connMgr.setDefaultMaxPerRoute(MAX_PER_ROUTE);
		
		RequestConfig requestConfig = RequestConfig.custom()
					//.setCookieSpec(CookieSpecs.IGNORE_COOKIES)
					.setSocketTimeout(TIMEOUT)
					.setConnectTimeout(TIMEOUT)
					.setConnectionRequestTimeout(TIMEOUT)
					.build();
		
		httpClient = HttpClients.custom()
							.setConnectionManager(connMgr)
							.setDefaultRequestConfig(requestConfig)
							.build();
	}
	
	/**
	 * HTTP 调用 GET 方法
	 * 
	 * @param url 网址
	 * @param headers HTTP头数组
	 * @return HTTP 调用返回字符串内容
	 * @throws IOException IO异常时抛出
	 * @throws HttpException HTTP调用异常时抛出
	 */
	public String get(String url, Header...headers) throws IOException, HttpException
	{
		return this.get(url, null, headers);
	}
	
	/**
	 * HTTP 调用 GET 方法
	 * 
	 * @param url 网址
	 * @param proxy 代理服务器
	 * @param headers HTTP头数组
	 * @return HTTP 调用返回字符串内容
	 * @throws IOException IO异常时抛出
	 * @throws HttpException HTTP调用异常时抛出
	 */
	public String get(String url, Proxy proxy, Header...headers) throws IOException, HttpException
	{
		HttpGet request = new HttpGet(url);
		if (headers.length > 0)
		{
			request.setHeaders(headers);
		}
		String host = UrlUtil.getHost(url);
		request.setHeader("User-Agent", USER_AGENT);
		request.setHeader("Accept", "*/*");
		request.setHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");
		request.setHeader("Host", host);
		request.setHeader("Referer", "http://" + host);

		HttpClientContext context = null;
		if (proxy != null)
		{
			context = HttpClientContext.create();
			context.setAttribute(PROXY, proxy);
		}
		return this.request(url, request, context);
	}
	
	
	
	/**
	 * HTTP 统一请求
	 * 
	 * @param url 网址
	 * @param request 请求方式，GET或POST
	 * @param context 请求上下文
	 * @return HTTP 调用返回字符串内容
	 * @throws IOException IO异常时抛出
	 * @throws HttpException HTTP调用异常时抛出
	 */
	private String request(String url, HttpRequestBase request, HttpClientContext context) throws IOException, HttpException
	{
		//Stream.of(request.getAllHeaders()).forEach(System.out::println);
		try (CloseableHttpResponse response = httpClient.execute(request, context);)
		{
			StatusLine statusLine = response.getStatusLine();
			String result = EntityUtils.toString(response.getEntity(), Consts.UTF_8);
			
			if (statusLine.getStatusCode() != HttpStatus.SC_OK) // http调用失败
			{
				logger.error("错误信息：{}－{}", result, statusLine);
				throw new HttpException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
			}
			
			return result;
		}
	}
	
	public CloseableHttpClient getHttpClient()
	{
		return httpClient;
	}
	
	public void destroy()
	{
		try
		{
			httpClient.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		connMgr.shutdown();
	}
	
	private class ProxyPlainConnectionSocketFactory extends PlainConnectionSocketFactory
	{
		@Override
		public Socket createSocket(HttpContext context) throws IOException
		{
			Proxy proxy = (Proxy) context.getAttribute(PROXY);
			if (proxy != null)
				return new Socket(proxy);
			else
				return super.createSocket(context);
		}
		
	}
	
	private class ProxySSLConnectionSocketFactory extends SSLConnectionSocketFactory
	{
		public ProxySSLConnectionSocketFactory(final SSLContext sslContext)
		{
			super(sslContext);
		}
		
		@Override
		public Socket createSocket(HttpContext context) throws IOException
		{
			Proxy proxy = (Proxy) context.getAttribute(PROXY);
			if (proxy != null)
				return new Socket(proxy);
			else
				return super.createSocket(context);
		}
		
	}
	
}
